package com.leyou.auth.service;

import com.leyou.auth.config.JwtProperties;
import com.leyou.auth.entity.ApplicationInfo;
import com.leyou.auth.mapper.AppInfoMapper;
import com.leyou.common.auth.entity.AppInfo;
import com.leyou.common.auth.entity.Payload;
import com.leyou.common.auth.entity.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.clients.UserClient;
import com.leyou.user.pojo.UserDTO;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

@Service
@EnableConfigurationProperties(JwtProperties.class)
public class AuthService {


    @Autowired
    private UserClient userClient;

    @Autowired
    private JwtProperties props;

    public void login(String username, String password, HttpServletResponse response) {

        //根据用户名和密码去跨服务查询用户的信息
        UserDTO userDTO = this.userClient.queryUserByNameAndPass(username, password);

        UserInfo userInfo = BeanHelper.copyProperties(userDTO, UserInfo.class);

        userInfo.setRole("假role");
        generateCookie(response, userInfo);
    }

    private void generateCookie(HttpServletResponse response, UserInfo userInfo) {
        // 获取私钥

        //使用私钥加密
        String token = JwtUtils.generateTokenExpireInMinutes(userInfo, props.getPrivateKey(), 30);

        //把加密生成的token存入cookie中
        CookieUtils.newBuilder()
                .name(props.getUser().getCookieName())
                .value(token)
                .maxAge(props.getUser().getCookieMaxAge())
                .domain(props.getUser().getCookieDomain())
                .path("/")
                .httpOnly(true)
                .response(response)
                .build();
    }

    public UserInfo verify(HttpServletRequest request, HttpServletResponse response) {

        String token = CookieUtils.getCookieValue(request, props.getUser().getCookieName());

        //使用公钥解析token
        Payload<UserInfo> infoFromToken = JwtUtils.getInfoFromToken(token, props.getPublicKey(), UserInfo.class);

        //判断tokenId是否在黑名单中，如果在则停止解析
        if (redisTemplate.hasKey(infoFromToken.getId())) {
            //cookie的删除,删除无效的假的cookie
            deleteCookie(response);
            throw new LyException(ExceptionEnum.INVALID_REQUEST_PARAM);
        }

        UserInfo userInfo = infoFromToken.getInfo();
        //刷新生成新的token以及cookie

        //如果cookie生成的时间，还不到1min，我拒绝再次生成

        //获取到过期时间的dateTime值，过期时间-29min和当前时间比，
        if (new DateTime(infoFromToken.getExpiration()).minusMinutes(29).isBeforeNow()) {
            generateCookie(response, userInfo);
        }

        return userInfo;
    }

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void logout(HttpServletRequest request, HttpServletResponse response) {

        String token = CookieUtils.getCookieValue(request, props.getUser().getCookieName());

        //公钥，解析
        // 获取公钥


        //使用公钥解析token
        Payload<UserInfo> infoFromToken = JwtUtils.getInfoFromToken(token, props.getPublicKey(), UserInfo.class);

        //如果过期时间-当前时间的差值超过了3s则，需要把tokenId加入黑名单
        long liveTime = infoFromToken.getExpiration().getTime() - System.currentTimeMillis();
        if (liveTime > 3000) {
            //如果过期时间比3s还要多，则存入redis，存储的时间就是剩余有效时间
            redisTemplate.opsForValue().set(infoFromToken.getId(), "", liveTime, TimeUnit.MILLISECONDS);
        }

        deleteCookie(response);

    }

    private void deleteCookie(HttpServletResponse response) {
        Cookie cookie = new Cookie(props.getUser().getCookieName(), "");
        cookie.setDomain(props.getUser().getCookieDomain());
        cookie.setPath("/");
        cookie.setMaxAge(0);
        response.addCookie(cookie);
    }

    @Autowired
    private AppInfoMapper appInfoMapper;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    //首先要校验，id和secret，把生成的token以string返回，至于客户端如何保存我们不管
    public String authenticate(Long id, String secret) {

        ApplicationInfo applicationInfo = this.appInfoMapper.selectByPrimaryKey(id);

        //验密，通过，要生成token,根据id查询没有查到，或者密码不匹配则都要抛出异常，状态码400，给定的请求参数有误
        if (null == applicationInfo || !passwordEncoder.matches(secret, applicationInfo.getSecret())) {
            throw new LyException(ExceptionEnum.INVALID_REQUEST_PARAM);
        }

        AppInfo appInfo = new AppInfo();
        appInfo.setId(id);
        appInfo.setServiceName(applicationInfo.getServiceName());
        //编写查询逻辑，去查询当前服务可以请求的目标服务的id集合
        appInfo.setTargetList(this.appInfoMapper.selectTargetList(id));

        //生成token并返回，appInfo，私钥，以及过期时间（25H）
        return JwtUtils.generateTokenExpireInMinutes(appInfo, props.getPrivateKey(), props.getApp().getExpire());

    }
}
